home *** CD-ROM | disk | FTP | other *** search
/ Linux Cubed Series 7: Sunsite / Linux Cubed Series 7 - Sunsite Vol 1.iso / system / shells / scsh-0.4 / scsh-0 / scsh-0.4.2 / scsh / time1.c < prev    next >
C/C++ Source or Header  |  1995-10-26  |  13KB  |  409 lines

  1. /* Posix time support for scsh.
  2. ** Copyright (c) 1994 by Olin Shivers.
  3. */
  4.  
  5. /* WARNING: THIS FILE HAS CODE THAT DEPENDS ON 32-BIT ARCHITECTURES.
  6. ** This code is so marked.
  7. **
  8. ** The source code is also conditionalised by three #ifdef feature macros:
  9. ** HAVE_TZNAME
  10. **   The char *tzname[2] global variable is POSIX. Everyone provides
  11. **   it...except some "classic" versions of SunOS that we still care about
  12. **   running (People in LCS/AI refuse to switch to Solaris). So, we kluge
  13. **   around not having it.
  14. **
  15. ** HAVE_GMTOFF
  16. **   Some systems (NetBSD, NeXTSTEP, Solaris) have a non-standard field in the
  17. **   tm struct, the tm_gmtoff field. localtime() sets it to the offset from
  18. **   UTC for the current time. If you have this field, it is trivial to
  19. **   compute the the UTC time zone offset. If you have a strict POSIX system,
  20. **   and don't have it, then the offset can be computed with a slower
  21. **   technique.
  22. **
  23. ** NeXT
  24. **   The presence of this feature macro means that, basically, you are
  25. **   screwed, and should go download yourself a real Unix system off the
  26. **   Net. For free.
  27. **
  28. **   More specifically, it means that (1) the presence of the strftime()
  29. **   function will cause the whole system build to die at link time,
  30. **   when compiled with the -posix flag. (NeXT bug #59098) There is no fix
  31. **   for this as of November 1994.  Thanks, guys.
  32. **   
  33. **   We handle this problem by abandoning ship. When compiled under NeXT, 
  34. **   your time zone is always computed to be the empty string.
  35. **
  36. **   The other problem is that (2) NeXT's mktime() procedure pays attention
  37. **   to the gmt_offset field of the tm struct you give it, instead of
  38. **   the $TZ environment variable. So there is no way to convert a date
  39. **   to a time without knowing in advance what the UTC offset is in seconds.
  40. **   This screws up scsh's DATE->TIME procedure.
  41. */
  42.  
  43. #include <time.h>
  44. #include <string.h>
  45. #include <errno.h>
  46. #include <stdlib.h>
  47.  
  48. #include "sysdep.h"
  49. #include "cstuff.h"
  50. #include "time1.h"    /* Make sure the .h interface agrees with the code. */
  51.  
  52. extern char **environ;
  53.  
  54. /* To work in the UTC time zone, do "environ = utc_env;". */
  55. static char *utc_env[] = {"TZ=UCT0", 0};
  56.  
  57. #ifdef HAVE_TZNAME
  58. extern char *tzname[];    /* Why isn't this defined in time.h? */
  59. #endif
  60.  
  61. /* These two functions allow you to temporarily override
  62. ** the current time zone with one of your choice. make_newenv()
  63. ** takes a time zone string as an argument, and constructs a Unix environ
  64. ** vector with a single entry: "TZ=<zone>". You pass the new environ vector
  65. ** as an argument. It installs the new environment, and returns the old
  66. ** one. You can later pass the old environment back to revert_env()
  67. ** to reinstall the old environment and free up malloc'd storage.
  68. **
  69. ** On error, make_newenv returns NULL.
  70. */
  71.  
  72. static char **make_newenv(scheme_value zone, char *newenv[2])
  73. {    
  74.     int zonelen = STRING_LENGTH(zone);
  75.     char **oldenv = environ,
  76.          *tz = Malloc(char, 4+zonelen);
  77.     if( !tz ) return NULL;
  78.     strcpy(tz, "TZ=");
  79.     strncpy(tz+3, &STRING_REF(zone,0), zonelen);
  80.     tz[zonelen+3] = '\0';
  81.     newenv[0] = tz;
  82.     newenv[1] = NULL;
  83.  
  84.     environ = newenv;    /* Install it. */
  85.     return oldenv;
  86.     }
  87.  
  88. static void revert_env(char **old_env)
  89. {
  90.     char *tz = *environ;
  91.     environ = old_env;
  92.     Free(tz);
  93.     }
  94.  
  95.  
  96. /*****************************************************************************/
  97.  
  98. /* Sux because it's dependent on 32-bitness. */
  99. #define hi8(i)  (((i)>>24) & 0xff)
  100. #define lo24(i) ((i) & 0xffffff)
  101. #define comp8_24(hi, lo) (((hi)<<24) + (lo))
  102.  
  103. scheme_value scheme_time(int *hi_secs, int *lo_secs)
  104. {
  105.     time_t t;
  106.     errno = 0;
  107.     t = time(NULL);
  108.     if( t == -1 && errno ) return ENTER_FIXNUM(errno);
  109.     *hi_secs = hi8(t);
  110.     *lo_secs = lo24(t);
  111.     return SCHFALSE;
  112.     }
  113.  
  114. /* Zone:
  115. **   #f        Local time
  116. **   int    Offset from GMT in seconds.
  117. **   string    Time zone understood by OS.
  118. */
  119. scheme_value time2date(int hi_secs, int lo_secs, scheme_value zone,
  120.                int *sec, int *min, int *hour,
  121.                int *mday, int *month, int *year,
  122.                const char **tz_name, int *tz_secs,
  123.                int *summer,
  124.                int *wday, int *yday)
  125. {
  126.     time_t t = comp8_24(hi_secs, lo_secs);
  127.     struct tm d;
  128.  
  129.     if( FIXNUMP(zone) ) {            /* Offset from GMT in secs. */
  130.     int offset = EXTRACT_FIXNUM(zone);
  131.     t += EXTRACT_FIXNUM(zone);
  132.     d = *gmtime(&t);
  133.     *tz_name = NULL;
  134.     *tz_secs = offset;
  135.     }
  136.     else {
  137.     char *newenv[2], **oldenv = NULL;
  138.  
  139.     if( STRINGP(zone) ) {            /* Time zone */
  140.         oldenv = make_newenv(zone, newenv);     /* Install new TZ. */
  141.         if( !oldenv ) return ENTER_FIXNUM(errno);    /* Error installing. */
  142.         d = *localtime(&t);                   /* Do it. */
  143.         }
  144.     else                    /* Local time */
  145.         d = *localtime(&t);
  146.  
  147.     /* This little chunk of code copies the calculated time zone into
  148.     ** a malloc'd buffer and assigns it to *tz_name. It's a little
  149.     ** complicated because we have to clean up after detecting an
  150.     ** error w/o walking on errno.
  151.     **
  152.     ** The time zone has to be stashed into a malloc'd buffer because
  153.     ** when revert_env resets to the original time zone, it will
  154.     ** overwrite the static buffer tzname. We have to copy it out before
  155.     ** that happens.
  156.     */
  157.     { int error = 0;
  158. #ifndef HAVE_TZNAME
  159.       char *zone = d.tm_zone; /* Hack it for SunOS. */
  160. #else
  161.       char *zone = tzname[d.tm_isdst];
  162. #endif
  163.       char *newzone = Malloc(char, 1+strlen(zone));
  164.       *tz_name = newzone;
  165.       if( newzone ) strcpy(newzone, zone);
  166.       else error = errno;
  167.       
  168.       if( oldenv ) revert_env(oldenv);        /* Revert TZ & env. */
  169.  
  170.       if( !newzone ) return ENTER_FIXNUM(error);
  171.       }
  172.  
  173.     /* Calculate the time-zone offset in seconds from UTC. */
  174. #ifdef HAVE_GMTOFF    
  175.     *tz_secs = d.tm_gmtoff;
  176. #else
  177.     { char **oldenv = environ;            /* Set TZ to UTC     */
  178.       environ=utc_env;                /* time temporarily. */
  179.       tzset(); /* NetBSD, SunOS POSIX-noncompliance requires this. */
  180.       *tz_secs = mktime(&d) - t;
  181.       environ=oldenv;
  182.       }
  183. #endif
  184.     }
  185.  
  186.     *sec  = d.tm_sec;    *min   = d.tm_min;    *hour   = d.tm_hour;
  187.     *mday = d.tm_mday;    *month = d.tm_mon;    *year   = d.tm_year;
  188.     *wday = d.tm_wday;    *yday  = d.tm_yday;    *summer = d.tm_isdst;
  189.     return SCHFALSE;
  190. }
  191.  
  192.  
  193. scheme_value date2time(int sec, int min, int hour,
  194.                int mday, int month, int year,
  195.                scheme_value tz_name, scheme_value tz_secs,
  196.                int summer,
  197.                int *hi_secs, int *lo_secs)
  198. {
  199.     time_t t;
  200.     struct tm d;
  201.     int error = 0;
  202.  
  203.     d.tm_sec  = sec;    d.tm_min  = min;    d.tm_hour  = hour;
  204.     d.tm_mday = mday;    d.tm_mon  = month;    d.tm_year  = year;
  205.     d.tm_wday = 0;    d.tm_yday = 0;        d.tm_isdst = summer;
  206.  
  207.     if( FIXNUMP(tz_secs) ) {        /* Offset from GMT in seconds. */
  208.     char **oldenv = environ;            /* Set TZ to UTC     */
  209.     environ = utc_env;                /* time temporarily. */
  210.     tzset(); /* NetBSD, SunOS POSIX-noncompliance requires this. */
  211.     errno = 0;    /* A -1 ret value from mktime() might be legal; */
  212.     t = mktime(&d); /*   hack errno to disambiguate. Ugh.           */
  213.     if( t == -1 && errno ) error = errno;
  214.     t -= EXTRACT_FIXNUM(tz_secs);
  215.     environ = oldenv;
  216.     }
  217.  
  218.     else if( STRINGP(tz_name) ) {    /* Time zone */
  219.     char *newenv[2];
  220.     char **oldenv = make_newenv(tz_name, newenv);
  221.     if( !oldenv ) return ENTER_FIXNUM(errno);
  222.     tzset(); /* NetBSD, SunOS POSIX-noncompliance requires this. */
  223.     errno = 0;    /* A -1 ret value from mktime() might be legal; */
  224.     t = mktime(&d); /*   hack errno to disambiguate. Ugh.           */
  225.     if( t == -1 && errno ) error = errno;
  226.     revert_env(oldenv);
  227.     }
  228.  
  229.     else {                /* Local time */
  230.     tzset(); /* NetBSD, SunOS POSIX-noncompliance requires this. */
  231.     t = mktime(&d);
  232.     if( t == -1 && errno ) error = errno;
  233.     }
  234.  
  235.     if( error ) return ENTER_FIXNUM(error);
  236.  
  237.     *hi_secs = hi8(t);
  238.     *lo_secs = lo24(t);
  239.     return SCHFALSE;
  240.     }
  241.  
  242.  
  243. /* WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING
  244. **
  245. ** This code doesn't work under NeXTSTEP.  I have cleverly #included the
  246. ** critical call to strftime() out for NeXT. This is because the compiler
  247. ** blows up on Posix compiles involving strftime(). Go figure.
  248. */
  249.  
  250.  
  251. /* It's disgusting how long and tortuous this function is, just
  252. ** to interface to the strftime() function. -Olin
  253. **
  254. ** There's a weird screw case this code is careful to handle. Exhibiting
  255. ** classic Unix design (we use the term loosely), strftime()'s error
  256. ** return (0) is also a legal return value for some boundary cases.
  257. ** For example, if the format string is empty, or it is "%Z" and
  258. ** the time-zone is not available, then the result string is 0 chars long.
  259. ** We distinguish this case by suffixing an "x" to the format string,
  260. ** and flushing the last char in the formatted result.
  261. **
  262. ** Don't consider *prefixing* an "x" instead, because then you'd
  263. ** probably pass back &result[1] to skip the x, and that would lose --
  264. ** the guy we are handing the string to will later pass it to free(),
  265. ** so we can't pass back a pointer to anything other than the very front
  266. ** of the block.
  267. **
  268. ** Professional programmers sacrifice their pride that others may live.
  269. ** Why me? Why Unix?
  270. */
  271. scheme_value format_date(const char *fmt, int sec, int min, int hour,
  272.              int mday, int month, int year,
  273.              scheme_value tz, int summer,
  274.              int week_day, int year_day,
  275.              const char **ans)
  276. {
  277.     struct tm d;
  278.     int fmt_len = strlen(fmt);
  279.     char *fmt2 = Malloc(char, 2+2*fmt_len); /* 1 extra for prefixed "x" char.*/
  280.     int target_len = 1;     /* 1 for the prefixed "x" char. Ugh. */
  281.     int zone = 0;         /* Are we using the time-zone? */
  282.     char *q, *target;
  283.     const char *p;
  284.     char *newenv[2], **oldenv = NULL;
  285.     int result_len;
  286.     
  287.     *ans = NULL;    /* In case we error out. */
  288.     if( !fmt2 ) return ENTER_FIXNUM(errno);
  289.  
  290.     d.tm_sec  = sec;        d.tm_min  = min;    d.tm_hour  = hour;
  291.     d.tm_mday = mday;        d.tm_mon  = month;    d.tm_year  = year;
  292.     d.tm_wday = week_day;    d.tm_yday = year_day;    d.tm_isdst = summer;
  293.  
  294.     /* Copy fmt -> fmt2, converting ~ escape codes to % escape codes.
  295.     ** Set zone=1 if fmt has a ~Z.
  296.     ** Build up an estimate of how large the target buffer needs to be.
  297.     ** The length calculation is not required to be accurate.
  298.     */
  299.     for(q=fmt2, p=fmt; *p; p++) {
  300.     if( *p != '~' ) {
  301.         target_len++;
  302.         *q++ = *p;
  303.         if( *p == '%' ) *q++ = '%';    /* Percents get doubled. */
  304.         }
  305.     else {
  306.         char c = *++p;
  307.         if( ! c ) {
  308.         Free(fmt2);
  309.         return SCHTRUE;    /* % has to be followed by something. */
  310.         }
  311.         else if( c == '~' ) {
  312.         *q++ = '~';
  313.         target_len++;
  314.         }
  315.         else {
  316.         *q++ = '%';
  317.         *q++ = c;
  318.         switch (c) {
  319.             case 'a': target_len += 3;  break;
  320.             case 'A': target_len += 9;  break;
  321.             case 'b': target_len += 3;  break;
  322.             case 'B': target_len += 9;  break;
  323.             case 'c': target_len += 10; break;    /* wtf */
  324.             case 'd': target_len += 2;  break;
  325.             case 'H': target_len += 2;  break;
  326.             case 'I': target_len += 2;  break;
  327.             case 'j': target_len += 3;  break;
  328.             case 'm': target_len += 2;  break;
  329.             case 'M': target_len += 2;  break;
  330.             case 'p': target_len += 2;  break;
  331.             case 'S': target_len += 2;  break;
  332.             case 'U': target_len += 2;  break;
  333.             case 'w': target_len += 1;  break;
  334.             case 'W': target_len += 2;  break;
  335.             case 'x': target_len += 10; break;    /* wtf */
  336.             case 'X': target_len += 10; break;    /* wtf */
  337.             case 'y': target_len += 2;  break;
  338.             case 'Y': target_len += 4;  break;
  339.             case 'Z': target_len += 6;  zone++; break;
  340.           default:
  341.                  target_len += 5; break;    /* wtf */
  342.             }
  343.         }
  344.         }
  345.     }
  346.     *q++ = 'x'; *q = '\0'; /* Append the guard "x" suffix and nul-terminate. */
  347.  
  348.     /* Fix up the time-zone if it is being used and the user passed one in. */
  349.     if( zone && STRINGP(tz) ) {
  350.     oldenv = make_newenv(tz, newenv);
  351.     if( !oldenv ) {
  352.         int err = errno;
  353.         Free(fmt);
  354.         return ENTER_FIXNUM(err);
  355.         }
  356.     }
  357.  
  358.     /* Call strftime with increasingly larger buffers until the result fits. */
  359.     target = Malloc(char, target_len);
  360.     if( !target ) goto lose; /* Alloc lost. */
  361.     
  362. #ifndef NeXT
  363.     while( !(result_len=strftime(target, target_len, fmt2, &d)) ) {
  364.     target_len *= 2;
  365.     target = Realloc(char, target, target_len);
  366.     if( !target ) goto lose;
  367.     }
  368.     target[result_len-1] = '\0'; /* Flush the trailing "x". */
  369. #endif
  370.     *ans = target;
  371.     Free(fmt2);
  372.     if( oldenv ) revert_env(oldenv);
  373.     return SCHFALSE;
  374.  
  375. lose:
  376.     /* We lost trying to allocate space for the strftime() target buffer. */
  377.     {int err = errno;
  378.      if( oldenv ) revert_env(oldenv); /* Clean up */
  379.      Free(fmt2);
  380.      return ENTER_FIXNUM(err);
  381.      }
  382. }
  383.  
  384. #if 0
  385. /* This is a kludge one can use should the tzname variable
  386. ** not be present on the system. Only SunOS is broken this way,
  387. ** and it has a non-standard alternative we can use for this application.
  388. ** So this code is commented out.
  389. **
  390. ** tzname_loser(int dst) returns a string containing the current time zone
  391. ** for loser OS's. The string is statically allocated. If the time zone
  392. ** is longer than some hidden, arbitrary length, the function simply
  393. ** returns the empty string. It is a workaround for tzname[dp->tm_isdst].
  394. ** 
  395. */
  396. char *tzname_loser(struct tm *dp)
  397. {
  398.     static char buf[1024];
  399.     return strftime(buf, 1024, "x%Z", dp) ? buf+1 : "";
  400.     }
  401. #endif
  402.  
  403. /* clear errno before mktime() and time(), if -1 ret, return errno.
  404. **     This is defined to work under HP-UX at least; 
  405. **     other man pages are silent.
  406. ** gettimeofday() returns -1/errno
  407. ** localtime() & gmtime() don't error.
  408. */
  409.